<?php
namespace trade\common\service;
ini_set('default_socket_timeout', -1);

use Carbon\Carbon;
/**
 * 生成K线
 *
 * Class KlineService
 */
class KlineService
{
    public static $redis = null;
    /**
     * Kline周期——1min、5min、15min、30min、60min、240min、日线d、周线w、月线m 年线y
     */
    protected $cycles = [
        '1分钟' => 1,
        '5分钟' => 5,
        '10分钟' => 10,
        '15分钟' => 15,
        '30分钟' => 30,
        '1小时' => 60,
        '2小时' => 120,
        '4小时' => 240,
        '日K' => 'day',
        '周K' => 'week',
        '月K' => 'month',
        '年K' => 'year'
    ];

    public function __construct($host='127.0.0.1',$port=6379)
    {
        if( empty(static::$redis) ){
            static::$redis = new \Redis();
            static::$redis->pconnect($host,$port);
        }
    }


    /**
     * 存储ticket和kline
     **/
    public function save($ticket,$prex='trade',$func=null){
        if( !empty($ticket)&&!empty($ticket['ctm'])&&!empty($ticket['code']) ){
            $this->saveKline($ticket,$prex);
            if( $this->saveTicket($ticket,$prex) && is_callable($func) )
            {
                $func($ticket,$prex);
            }
        }
    }

    /**
     * 获取最新报价
     */
    public function loadTicekt($code=null,$prex='trade'){
        if( $code ){
            if( static::$redis->hexists("{$prex}_ticket",$code) ){
                return json_decode(static::$redis->hget("{$prex}_ticket",$code),true);
            }
        }else{
            if( static::$redis->exists("{$prex}_ticket") ){
                return static::$redis->hGetAll("{$prex}_ticket");
            }
        }
        return [];
    }

    /**
     * 获取Kline
     */
    public function loadKline($code,$type,$prex='trade',$limit = 500){
        return static::$redis->lrange("{$prex}_kline_{$code}_{$type}",0,$limit);
    }


    /**
     * 存储ticket
     **/
    public function saveTicket($ticket,$prex='trade')
    {
    	$_ticket = static::$redis->hget("{$prex}_ticket",$ticket['code']);
    	if( empty($_ticket) ){
    		return static::$redis->hset("{$prex}_ticket",$ticket['code'],json_encode($ticket));
    	}else{
    		$_ticket = json_decode($_ticket,true);static::$redis->hset("{$prex}_ticket",$ticket['code'],json_encode($ticket));
    		if( $_ticket['price']==$ticket['price'] && $_ticket['ask']==$ticket['ask'] && $_ticket['bid']==$ticket['bid'] ){
    			return false;
    		}else{
    			return true;
    		}
    	}
    }

    /**
     * 存储kline
     **/
    public function saveKline( $ticket,$prex='trade', $is_ask = true){

        foreach ( $this->cycles as $cycle )
        {
            $kline = static::$redis->lrange("{$prex}_kline_{$ticket['code']}_{$cycle}",0,0);
            $kline = empty($kline[0])?(Object)['open'=>0,'high'=>0,'close'=>0,'low'=>0,'wave'=>0,'volume' => 0]:json_decode($kline[0]);

            $price      = bcadd($is_ask?$ticket['ask']:$ticket['bid'], 0, $ticket['digit']);
            $thisctmfmt = $this->getKey($cycle, $ticket['ctmfmt']);
            $thisctm    = strtotime($thisctmfmt);
            if( $thisctm ){
                if( !empty($kline->ctm) && $thisctm==$kline->ctm){
                    $kline->high     = max($kline->open, $kline->high, $kline->low, $kline->close, $price);
                    $kline->low      = min($kline->open, $kline->high, $kline->low, $kline->close, $price);
                    $kline->close    = $price;
                    $kline->wave     = @$ticket['wave'];
                    $kline->volume   = @$ticket['volume'];
                    $kline->price    = @$ticket['price'];
                    $kline->ctm      = $thisctm;
                    $kline->ctmfmt   = $thisctmfmt;
                    static::$redis->lSet("{$prex}_kline_{$ticket['code']}_{$cycle}",0,json_encode($kline));
                }else{
                    $kline->open     = $price;
                    $kline->high     = $price;
                    $kline->low      = $price;
                    $kline->close    = $price;
                    $kline->wave     = @$ticket['wave'];
                    $kline->volume   = @$ticket['volume'];
                    $kline->price    = @$ticket['price'];
                    $kline->ctm      = $thisctm;
                    $kline->ctmfmt   = $thisctmfmt;
                    static::$redis->lPush("{$prex}_kline_{$ticket['code']}_{$cycle}",json_encode($kline));
                }
                if( static::$redis->lLen("{$prex}_kline_{$ticket['code']}_{$cycle}")>500 ){
                    static::$redis->brPop("{$prex}_kline_{$ticket['code']}_{$cycle}",500);
                }
            }
        }
    }

    public function getKey($m = 0, $datetime, $previousKey = false){
        $datetime  = Carbon::parse($datetime);
        $nowHour   = strval($datetime->hour);
        $nowMinute = strval($datetime->minute);
        if (is_numeric($m))
        {
            if ($m < 60) {
                //1分钟
                if ($m == 1) {
                    if ($previousKey) {
                        return $Key = $datetime->subMinute(1)->format('Y-m-d H:i');
                    }
                    return $Key = $datetime->format('Y-m-d H:i');
                }

                $Minlist = [0];
                $nums    = bcdiv(60, $m);
                for ($i = 0; $i < $nums; $i++) {
                    $last = end($Minlist);
                    array_push($Minlist, (int)bcadd($m, $last));
                }
                foreach ($Minlist as $lk => $lv) {
                    if ($nowMinute <= $lv) {
                        $nowDate = $datetime;
                        if ($nowMinute == $lv) {
                            $nowDate->setTime($nowDate->hour, $lv, 0);
                        }
                        else {
                            $nowDate->setTime($nowDate->hour, $Minlist[$lk-1], 0);
                        }
                        $Key = $nowDate->format('Y-m-d H:i');
                        if ($previousKey) {
                            $Key = $nowDate->subMinute($m)->format('Y-m-d H:i');
                        }
                        return $Key;
                    }
                }
            }

            if ($m == 60) {
                $nowDate         = $datetime;
                $nowDate->minute = 0;
                $Key             = date('Y-m-d H:i', $nowDate->timestamp);
                if ($previousKey) {
                    $Key = Carbon::parse(date('Y-m-d H:i', $nowDate->timestamp))->subHour(1)->format('Y-m-d H:i');
                }
                return $Key;
            }

            if ($m > 60) {
                $HourList = [0];
                if ($m == 240) {
                    $nums = bcdiv(bcmul(24, 60), $m);
                    for ($i = 0; $i < $nums; $i++) {
                        $last = end($HourList);
                        array_push($HourList, (int)bcadd($last, 4));
                    }
                    foreach ($HourList as $lk => $lv) {
                        if ($nowHour <= $lv) {
                            $nowDate = $datetime;
                            if ($nowHour == $lv) {
                                $nowDate->setTime($lv, 0, 0);
                            }
                            else {
                                $nowDate->setTime($HourList[$lk - 1], 0, 0);
                            }
                            $Key = $nowDate->format('Y-m-d H:i');
                            if ($previousKey) {
                                $Key = $nowDate->subMinute($m)->format('Y-m-d H:i');
                            }
                            return $Key;
                        }
                    }
                }
            }
        }else if ($m == 'day') {
            if ($previousKey) {
                return $datetime->subDay(1)->format('Y-m-d');
            }
            return $datetime->format('Y-m-d');
        }else if ($m == 'week') {
            if ($previousKey) {
                return $datetime->startOfWeek()->subWeek(1)->format('Y-m-d');
            }
            return $datetime->startOfWeek()->format('Y-m-d');
        }else if ($m == 'month') {
            if ($previousKey) {
                return $datetime->startOfMonth()->subMonth(1)->format('Y-m-d');
            }
            return $datetime->startOfMonth()->format('Y-m-d');
        }else if ($m == 'year') {
            if ($previousKey) {
                return $datetime->startOfYear()->subYear(1)->format('Y-m-d');
            }
            return $datetime->startOfYear()->format('Y-m-d');
        }
    }
}